#include <iostream>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include "Master.h"
#include "Router.h"
#include "Socket.h"
#include "Log.h"
#include "Script.h"
#include "Utils.h"
#include "Atomic.h"

NS_USING;

extern char **environ;

Master* Master::pInstance = NULL;

Master::Master():
	pServer(NULL),
	pWorkers(NULL),
	pGlobals(NULL),
	nServerId(0),
	nMaxBackendWeights(0)
{
}

Master::~Master()
{
	delete pServer;
	
	if(pWorkers){
		for(int i = 0; i < nWorkers; ++i){
			delete pWorkers[i];
		}
		zfree(pWorkers);
	}
	
	releaseGlobals();
}

Master*	Master::getInstance()
{
	if(!pInstance){
		pInstance = new Master();
	}
	return pInstance;
}

void Master::start(int argc, char **argv)
{
	//args check
	if(argc < 2){
		printf("usage: fooking config.lua\n");
		return ;
	}
	
	nArgc = argc;
	pArgv = argv;
	initProcTitle();
	
	//config init
	sConfigFile = argv[1];
	if(!loadConfig()){
		return ;
	}
	
	//log init
	if(sLogFile.size() > 0){
		if(sLogFile == "stdout"){
			Log::init(nLogLevel, STDOUT_FILENO);
		}else if(sLogFile == "stderr"){
			Log::init(nLogLevel, STDERR_FILENO);
		}else if(!Log::init(nLogLevel, sLogFile.c_str())){
			printf("init log failed\n");
			return ;
		}
	}
	
	//daemonize
	if(bDaemonize){
		daemonize();
	}
	
	//session initialize
	Session::init();
	
	//server
	if(bRouter){
		//create router
		setProcTitle("fooking router server");
		Router *pRouter = new Router(this);
		pRouter->start();
	}else{
		//create share memory
		if(!createGlobals()){
			return ;
		}
		
		//create server
		pServer = new Server(NULL);
		if(pServer->createTcpServer(nPort) != 0){
			printf("create tcp server failed, port=%d, errno=%d, errstr=%s\n", 
				nPort, errno, strerror(errno));
			return ;
		}
		
		//create worker
		pWorkers = (Worker**)zmalloc(nWorkers * sizeof(Worker*));
		bAcceptLock = nWorkers > 1;
		for(int i = 0; i < nWorkers; ++i){
			pWorkers[i] = new Worker(this, i);
			pWorkers[i]->start();
		}
		
		setProcTitle("fooking gateway master");
		LOG_INFO("server started, listenfd=%d", pServer->getSocket().getFd());
		
		//init signal
		setupSignal();

		//wait worker exit
		while(true)
		{
			int ret = 0;
			int pid = ::wait(&ret);
			if(pid <= 0){
				continue;
			}
			
			int found = -1;
			for(int i = 0; i < nWorkers; ++i){
				Worker *pWorker = pWorkers[i];
				if(pWorker->getPid() == pid){
					found = pWorker->id();
					delete pWorker;
					break;
				}
			}
			
			if(found == -1){
				LOG_ERR("worker exited, not found workerid");
			}else{
				LOG_INFO("worker exited, id=%d, pid=%d", found, pid);
				atomic_fetch_sub(&pGlobals->clients, pGlobals->workerClients[found]);
				pGlobals->workerClients[found] = 0;
				
				//free lock
				if(bAcceptLock){
					UnLockAcceptMutex(&pGlobals->lock, pid);
				}
				
				//restart
				pWorkers[found] = new Worker(this, found);
				pWorkers[found]->start();
			}
		}
	}
}

void Master::daemonize()
{
	if (fork() != 0){
		exit(0); /* parent exit */
	}

	setsid();

	int fd;
	if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
		dup2(fd, STDIN_FILENO);
		dup2(fd, STDOUT_FILENO);
		dup2(fd, STDERR_FILENO);
		if (fd > STDERR_FILENO) close(fd);
	}
}

void Master::setupSignal()
{
	struct sigaction act;
	
	signal(SIGHUP, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = Master::procSignal;
	sigaction(SIGTERM, &act, NULL);
	sigaction(SIGUSR1, &act, NULL);
}

void Master::procSignal(int sig)
{
	Master *pThis = Master::getInstance();
	LOG_INFO("master receive signal=%d", sig);
	switch(sig){
		case SIGUSR1:
			for(int i = 0; i < pThis->nWorkers; ++i){
				ChannelMsg ch;
				ch.type = CH_RELOAD;
				ch.fd = 0;
				ch.pid = 0;
				pThis->pWorkers[i]->send(&ch);
			}
			break;
		default:
			kill(0, SIGKILL);
			break;
	}
}

bool Master::loadConfig()
{
	Script s;
	if(!s.open(sConfigFile.c_str())){
		return false;
	}
	
	//basic option
	lua_State *L = s.getHandle();
	sHost = s.readString("HOST");//host
	nPort = s.readInt("PORT");//port
	bDaemonize = s.readBoolean("DAEMONIZE");
	sLogFile = s.readString("LOG_FILE");//log file
	nLogLevel = s.readInt("LOG_LEVEL");//log level
	bRouter = s.readBoolean("ROUTER");//router server
	
	//router == 1
	if(bRouter){
		return true;
	}
	
	//worker option
	nServerId = s.readInt("SERVER_ID");
	bEventConnect = s.readBoolean("EVENT_CONNECT");
	bEventClose = s.readBoolean("EVENT_CLOSE");
	nWorkers = s.readInt("WORKER_NUM");
	if(nWorkers < 1){
		printf("WORKER_NUM Invalid(WORKER_NUM>=1)\n");
		return false;
	}
	
	nMaxClients = s.readInt("MAX_CLIENT_NUM");
	if(nMaxClients < 1){
		printf("MAX_CLIENT_NUM Invalid(MAX_CLIENT_NUM>=1)\n");
		return false;
	}
	
	nSendBufferSize = s.readInt("SEND_BUFF_SIZE");
	if(nSendBufferSize < 1){
		printf("SEND_BUFF_SIZE Invalid(SEND_BUFF_SIZE>=1)\n");
		return false;
	}
	
	nRecvBufferSize = s.readInt("RECV_BUFF_SIZE");
	if(nRecvBufferSize < 1){
		printf("RECV_BUFF_SIZE Invalid(RECV_BUFF_SIZE>=1)\n");
		return false;
	}
	
	//暂时只能fastcgi
	nProtocol = s.readInt("PROTOCOL");
	
	//backend server list
	nBackendTimeout = s.readInt("BACKEND_TIMEOUT");
	lua_getglobal(L, "BACKEND_SERVER");
	if(lua_isstring(L, -1)){
		const char *val = lua_tostring(L, -1);
		if(!addBackendServer(val, 1)){
			printf("BACKEND_SERVER Invalid\n");
			return false;
		}
	}else if(lua_istable(L, -1)){
		lua_pushnil(L);
		while(lua_next(L, -2))
		{
			const char *str = NULL;
			int weight = 0;
			if(lua_isstring(L, -2)){
				str = lua_tostring(L, -2);
			}
			if(lua_isnumber(L, -1)){
				weight = lua_tonumber(L, -1);;
			}
			if(str && weight > 0){
				if(!addBackendServer(str, weight)){
					printf("BACKEND_SERVER Invalid\n");
					return false;
				}
			}
			lua_pop(L, 1);
		}
		lua_pop(L, 1);
	}else{
		printf("BACKEND_SERVER invalid\n");
		return false;
	}
	
	//router option
	sScript = s.readString("SCRIPT_FILE");
	sRouterHost = s.readString("ROUTER_HOST");
	nRouterPort = s.readInt("ROUTER_PORT");
	
	//fastcgi
	sFastcgiRoot = s.readString("FASTCGI_ROOT");
	sFastcgiFile = s.readString("FASTCGI_FILE");
	
	//fastcgi params
	lua_getglobal(L, "FASTCGI_PARAMS");
	if(lua_istable(L, -1)){
		lua_pushnil(L);
		while(lua_next(L, -2))
		{
			const char *key = NULL;
			const char *val = NULL;
			if(lua_isstring(L, -2)){
				key = lua_tostring(L, -2);
			}
			if(lua_isstring(L, -1)){
				val = lua_tostring(L, -1);
			}
			if(key && val){
				arFastcgiParams[key] = val;
			}
			lua_pop(L, 1);
		}
		lua_pop(L, 1);
	}else{
		printf("FASTCGI_PARAMS invalid\n");
		return false;
	}
	
	return true;
}

bool Master::addBackendServer(const char *str, int weight)
{
	SocketOption opt = Utils::parseSocket(str);
	BackendOption backend;
	switch(opt.type){
		case SOCKET_TCP:
			backend.type = SOCKET_TCP;
			backend.host = opt.tcp_host;
			backend.port = opt.tcp_port;
			break;
		case SOCKET_UNIX:
			backend.type = SOCKET_UNIX;
			backend.host = opt.unix_name;
			break;
		default:
			//Nothing todo;
			return false;
	}
	
	backend.weight = weight;
	arrBackendServer.push_back(backend);
	
	nMaxBackendWeights+= weight;
	
	return true;
}

bool Master::createGlobals()
{
	int size = sizeof(GlobalData) + sizeof(int) * nWorkers;
	int id = shmget(IPC_PRIVATE, size, (SHM_R|SHM_W|IPC_CREAT));
	if (id == -1) {
		LOG_ERR("shmget(%u) failed", size);
		return false;
	}

	pGlobals = (GlobalData*)shmat(id, NULL, 0);
	if(pGlobals == (void*)-1){
		LOG_ERR("shmat() failed");
		return false;
	}

	if (shmctl(id, IPC_RMID, NULL) == -1) {
		LOG_ERR("shmctl(IPC_RMID) failed");
		return false;
	}

	return true;
}

void Master::releaseGlobals()
{
	if(shmdt(pGlobals) == -1) {
		LOG_ERR("shmdt(%p) failed", pGlobals);
	}
}


void Master::initProcTitle()
{
	size_t size = 0;
	for (int i = 0; environ[i]; ++i) {
		size += strlen(environ[i]) + 1; 
	}

	char *raw = new char[size];
	for(int i = 0; environ[i]; ++i) {
		int envlen = strlen(environ[i]) + 1;
		memcpy(raw, environ[i], envlen);
		environ[i] = raw;
		raw+= envlen;
	}
}

void Master::setProcTitle(const char *title)
{
	char *p = pArgv[0];
	int tlen = strlen(title);
	strncpy(p, title, tlen);
	p[tlen] = '\0';
}